/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package collections.list;

import collections.list.interfaces.MyList;
import collections.exceptions.MyIndexOutOfBoundsException;
import collections.list.interfaces.MyQueue;
import collections.list.interfaces.MyStack;
import java.util.Iterator;
import java.util.LinkedList;

/**
 *
 * @author Iaroslav_Mazai
 */
public class MyLinkedList implements MyList, MyQueue, MyStack {

	@Override
	public void offer(Object object) {
		addLast(object);
	}

	@Override
	public Object peek() {
		return getLast();
	}

	@Override
	public Object poll() {
		return removeLast();
	}

	@Override
	public void push(Object object) {
		addFirst(object);
	}

	@Override
	public Object pop() {
		return removeLast();
	}

	private class Node {

		private Object value;
		private Node previous;
		private Node next;

		public Node(Node previous, Object value, Node next) {
			this.previous = previous;
			this.value = value;
			this.next = next;
		}

		public Object getValue() {
			return value;
		}

		public void setValue(Object value) {
			this.value = value;
		}

		public Node getPrevious() {
			return previous;
		}

		public void setPrevious(Node previous) {
			this.previous = previous;
		}

		public Node getNext() {
			return next;
		}

		public void setNext(Node next) {
			this.next = next;
		}

		@Override
		public String toString() {
			String result = "(";
			if (previous == null) {
				result += "null ";
			} else {
				result += previous.getValue() + "; ";
			}
			result += getValue() + "; ";
			if (next == null) {
				result += "null ";
			} else {
				result += next.getValue() + ")";
			}
			return result;
		}
	}
	private Node first;
	private Node last;
	public int length;

	public MyLinkedList() {
		length = 0;
	}

	public MyLinkedList(MyList c) {
		this();
		if (c.isEmpty()) {
			return;
		}
		first = new Node(null, c.get(0), null);
		for (Object object : c) {
			addLast(object);
		}
		length = c.size();
	}

	public void addFirst(Object o) {
		Node oldFirst = first;
		Node newFirst = new Node(null, o, first);
		first = newFirst;

		if (oldFirst == null) {
			last = newFirst;
		} else {
			oldFirst.setPrevious(newFirst);
		}
		length++;
	}

	public void addLast(Object o) {
		Node oldLast = last;
		Node newLast = new Node(last, o, null);
		last = newLast;

		if (oldLast == null) {
			first = newLast;
		} else {
			oldLast.setNext(last);
		}
		length++;
	}

	public Object getFirst() {
		if (first == null) {
			return null;
		}
		return first.getValue();
	}

	public Object getLast() {
		if (last == null) {
			return null;
		}
		return last.getValue();
	}

	public Object removeFirst() {
		Object oldFirst = first.getValue();
		first = first.getNext();
		first.setPrevious(null);
		length--;
		return oldFirst;
	}

	public Object removeLast() {
		Object oldLast = last.getValue();
		last = last.getPrevious();
		last.setNext(null);
		length--;
		return oldLast;
	}

	@Override
	public void add(Object e) {
		addLast(e);
	}

	private void checkIndex(int index) {
		if (index >= length) {
			throw new MyIndexOutOfBoundsException();
		}
	}

	@Override
	public void add(int index, Object element) {
		if (index == 0) {
			addFirst(element);
			return;
		}

		if (index == length) {
			addLast(element);
			return;
		}

		checkIndex(index);

		Node place = getNode(index);

		Node newNode = new Node(place.getPrevious(), element, place);
		place.getPrevious().setNext(newNode);
		place.setPrevious(newNode);
		length++;
	}

	@Override
	public void addAll(Object[] c) {
		for (Object object : c) {
			add(object);
		}
	}

	@Override
	public void addAll(int index, Object[] c) {
		for (int i = 0; i < c.length; i++) {
			add(index + i, c[i]);
		}
	}

	private Node getNode(int index) {
		checkIndex(index);

		int startDistance = index;
		int endDistance = size() - index - 1;

		if (startDistance < endDistance) {
			return getNodeFromStart(startDistance);
		} else {
			return getNodeFromEnd(endDistance);
		}
	}

	private Node getNodeFromStart(int index) {
		Node cursor = first;

		for (int i = 0; i < index; i++) {
			cursor = cursor.getNext();
		}

		return cursor;
	}

	private Node getNodeFromEnd(int index) {
		Node cursor = last;

		for (int i = 0; i < index; i++) {
			cursor = cursor.getPrevious();
		}

		return cursor;
	}

	@Override
	public Object get(int index) {
		checkIndex(index);

		Node place = getNode(index);
		return place.getValue();
	}

	private void removeNode(Node node) {
		if (node == null) {
			return;
		}
		if (node.getPrevious() != null) {
			node.getPrevious().setNext(node.getNext());
		}
		if (node.getNext() != null) {
			node.getNext().setPrevious(node.getPrevious());
		}
		length--;
	}

	@Override
	public Object remove(int index) {
		Node oldNode = getNode(index);
		Object oldObject = oldNode.getValue();
		removeNode(oldNode);
		return oldObject;
	}

	@Override
	public void clear() {
		first = last = null;
		length = 0;
	}

	@Override
	public boolean isEmpty() {
		return size() <= 0;
	}

	@Override
	public Object set(int index, Object e) {
		checkIndex(index);

		Node place = getNode(index);
		Object oldValue = place.getValue();
		place.setValue(e);
		return oldValue;
	}

	@Override
	public int indexOf(Object o) {
		Iterator iterator = iterator();
		int index = 0;
		while (iterator.hasNext()) {
			if (o == null) {
				if (iterator.next() == null) {
					return index;
				}
			} else {
				if (o.equals(iterator.next())) {
					return index;
				}
			}
			index++;
		}
		return -1;
	}

	@Override
	public int size() {
		return length;
	}

	@Override
	public Object[] toArray() {
		Object[] result = new Object[length];

		Iterator iterator = iterator();
		int i = 0;
		while (iterator.hasNext()) {
			result[i] = iterator.next();
			i++;
		}

		return result;
	}

	private class AscIterator implements Iterator {

		Node cursor;

		public AscIterator() {
			cursor = new Node(null, null, first);
		}

		@Override
		public boolean hasNext() {
			return cursor.getNext() != null;
		}

		@Override
		public Object next() {
			cursor = cursor.getNext();
			return cursor.getValue();
		}

		@Override
		public void remove() {
			removeNode(cursor);
		}
	}

	private class DescIterator implements Iterator {

		Node cursor;

		public DescIterator() {
			cursor = new Node(last, null, null);
		}

		@Override
		public boolean hasNext() {
			return cursor.getPrevious() != null;
		}

		@Override
		public Object next() {
			cursor = cursor.getPrevious();
			return cursor.getValue();
		}

		@Override
		public void remove() {
			removeNode(cursor);
		}
	}

	@Override
	public Iterator iterator() {
		return new AscIterator();
	}

	public Iterator descIterator() {
		return new DescIterator();
	}

	@Override
	public String toString() {
		if (isEmpty()) {
			return "[]";
		}
		String result = "[";
		Iterator iterator = iterator();
		while (iterator.hasNext()) {
			result += iterator.next() + ", ";
		}
		result = result.substring(0, result.length() - 2);
		result += "]";
		return result;
	}

	@Override
	public int hashCode() {
		int hash = 7;
		Iterator iterator = iterator();
		while (iterator.hasNext()) {
			hash += 3 * iterator.next().hashCode();
		}
		return hash;
	}

	@Override
	public boolean equals(Object obj) {
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		final MyLinkedList other = (MyLinkedList) obj;
		if (this.length != other.length) {
			return false;
		}
		
		Iterator thisIterator = iterator();
		Iterator otherIterator = other.iterator();
		while (thisIterator.hasNext()){
			Object thisValue = thisIterator.next();
			Object otherValue = otherIterator.next();
			if (!thisValue.equals(otherValue)){
				return false;
			}
		}
		return true;
	}
}
